﻿using System;
using System.Data;
using System.Collections;
using System.Text;
using System.Configuration;

namespace BizElements.Core
{
    #region Documentation.
    /// <summary>
    /// Determines if a given value matches any value in a subquery or a list.
    /// </summary>
    /// <remarks>InPredicate cannot be created by using its constructor.
    /// Use <see cref="PredicateFactory"/> to create predicates.
    /// <seealso cref="SearchCondition"/></remarks>
    /// <example>
    /// The following example selects all employees with the given IDs.
    /// <code>
    /// public DataTable SelectSpecifiedEmployees(int[] employeeIDs)
    /// {
    /// 	// IN predicate expects an array of objects.
    /// 	// int[] must be converted to object[]
    /// 	object[] oEmployeeIds = new object[employeeIDs.Length];
    /// 	employeeIDs.CopyTo(oEmployeeIds, 0);
    /// 
    /// 	EmployeesMeta employees = new EmployeesMeta();
    /// 	SelectStatement select = new SelectStatement(employees);
    /// 	select.Where.Add(PredicateFactory.In(employees.EmployeeID, oEmployeeIds));
    /// 	return select.Execute();
    /// }
    /// </code>
    /// </example>
    #endregion
    [Serializable]
    public sealed class InPredicate : PredicateBase
    {
        #region IN.

        /// <summary>
        /// Creates a new IN predicate.
        /// </summary>
        /// <param name="firstItem">First item. Test expression.</param>
        /// <param name="valueItems">Array of expressions (subqueries, values).</param>
        /// <param name="negate">Specifies whether the predicate is negated (NOT IN).</param>
        internal InPredicate(PredicateItem firstItem, PredicateItem[] valueItems, bool negate)
        {
            EnsureValueItemsAreValid(valueItems);
            PredicateItems = new PredicateItem[1 + valueItems.Length];
            PredicateItems[0] = firstItem;
            for (int idxValue = 0; idxValue < valueItems.Length; idxValue++)
                PredicateItems[idxValue + 1] = valueItems[idxValue];

            Negate = negate;
        }

        private static void EnsureValueItemsAreValid(PredicateItem[] valueItems)
        {
            if (valueItems == null || valueItems.Length == 0)
                throw new ArgumentException(Messages.InPredicate_AtLeastOneValueIsRequired, "valueItems");

            foreach (PredicateItem expression in valueItems)
            {
                if (expression == null)
                    throw new ArgumentException(Messages.InPredicate_NullsAreNotAllowedInArray, "valueItems");
            }
        }

        /// <summary>
        /// Renders predicate as SQL statement.
        /// </summary>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which SQL is appended.</param>
        /// <param name="parameters">SQL parameter collection to which the object's and its children's
        /// parameters are added. After the rendering is done the collection contains all parameters with unique names.</param>
        public override void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
        {
            output.Append("(");
            PredicateItem lhsItem = PredicateItems[0];
            lhsItem.Render(dbms, output, parameters);

            if (Negate)
                output.Append(" NOT IN (");
            else
                output.Append(" IN (");

            for (int inItemIdx = 1; inItemIdx < PredicateItems.Length - 1; inItemIdx++)
            {
                PredicateItems[inItemIdx].Render(dbms, output, parameters);
                output.Append(", ");
            }
            PredicateItem lastInItem = PredicateItems[PredicateItems.Length - 1];
            lastInItem.Render(dbms, output, parameters);

            output.Append("))");
        }

        #endregion
    }
}
